﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using OperationPlugins.Compositions;
using OperationPlugins.Models;

namespace OperationPlugins.Validators
{
    public static class Validator
    {
        /// <summary>
        /// Authorizes the handler.
        /// </summary>
        private static IEnumerable<IAuthorizeValidator> Authorizers { get; set; }

        /// <summary>
        /// Activates the validator.
        /// </summary>
        private static IValidatorActivator Activator { get; set; }

        /// <summary>
        /// Invokes the validator.
        /// </summary>
        private static IValidatorInvoker Invoker { get; set; }

        /// <summary>
        /// Validates an object.
        /// </summary>
        /// <param name="value">The object to be validated.</param>
        /// <returns>The ValidationResultMessageContainer object.</returns>
        public static ValidationResultMessageContainer Invoke(object value)
        {
            return Invoke(value, Operations.Any, Branches.Any, OperationContextManager.GetOperationContextFactory());
        }

        /// <summary>
        /// Validates an object.
        /// </summary>
        /// <param name="value">The object to be validated.</param>
        /// <param name="contextFactory">The IOperationContextFactory to be used.</param>
        /// <returns>The ValidationResultMessageContainer object.</returns>
        public static ValidationResultMessageContainer Invoke(object value, IOperationContextFactory contextFactory)
        {
            return Invoke(value, Operations.Any, Branches.Any, contextFactory);
        }

        /// <summary>
        /// Validates an object.
        /// </summary>
        /// <param name="value">The object to be validated.</param>
        /// <param name="operation">Identifies the current operation.</param>
        /// <returns>The ValidationResultMessageContainer object.</returns>
        public static ValidationResultMessageContainer Invoke(object value, OperationId operation)
        {
            return Invoke(value, operation, Branches.Any, OperationContextManager.GetOperationContextFactory());
        }

        /// <summary>
        /// Validates an object.
        /// </summary>
        /// <param name="value">The object to be validated.</param>
        /// <param name="operation">Identifies the current operation.</param>
        /// <param name="contextFactory">The IOperationContextFactory to be used.</param>
        /// <returns>The ValidationResultMessageContainer object.</returns>
        public static ValidationResultMessageContainer Invoke(object value, OperationId operation, IOperationContextFactory contextFactory)
        {
            return Invoke(value, operation, Branches.Any, contextFactory);
        }

        /// <summary>
        /// Validates an object.
        /// </summary>
        /// <param name="value">The object to be validated.</param>
        /// <param name="branch">Identifies the current branch.</param>
        /// <returns>The ValidationResultMessageContainer object.</returns>
        public static ValidationResultMessageContainer Invoke(object value, BranchId branch)
        {
            return Invoke(value, Operations.Any, branch, OperationContextManager.GetOperationContextFactory());
        }

        /// <summary>
        /// Validates an object.
        /// </summary>
        /// <param name="value">The object to be validated.</param>
        /// <param name="branch">Identifies the current branch.</param>
        /// <param name="contextFactory">The IOperationContextFactory to be used.</param>
        /// <returns>The ValidationResultMessageContainer object.</returns>
        public static ValidationResultMessageContainer Invoke(object value, BranchId branch, IOperationContextFactory contextFactory)
        {
            return Invoke(value, Operations.Any, branch, contextFactory);
        }

        /// <summary>
        /// Validates an object.
        /// </summary>
        /// <param name="value">The object to be validated.</param>
        /// <param name="operation">Identifies the current operation.</param>
        /// <param name="branch">Identifies the current branch.</param>
        /// <returns>The ValidationResultMessageContainer object.</returns>
        public static ValidationResultMessageContainer Invoke(object value, OperationId operation, BranchId branch)
        {
            return Invoke(value, operation, branch, OperationContextManager.GetOperationContextFactory());
        }

        /// <summary>
        /// Validates an object.
        /// </summary>
        /// <param name="value">The object to be validated.</param>
        /// <param name="operation">Identifies the current operation.</param>
        /// <param name="branch">Identifies the current branch.</param>
        /// <param name="contextFactory">The IOperationContextFactory to be used.</param>
        /// <returns>The ValidationResultMessageContainer object.</returns>
        public static ValidationResultMessageContainer Invoke(object value, OperationId operation, BranchId branch, IOperationContextFactory contextFactory)
        {
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }

            if (operation == null)
            {
                throw new ArgumentNullException("operation");
            }

            if (branch == null)
            {
                throw new ArgumentNullException("branch");
            }

            if (contextFactory == null)
            {
                throw new ArgumentNullException("contextFactory");
            }

            OperationPluginsEnvironment.CheckStatus();

            ValidationResultMessageContainer whole = new ValidationResultMessageContainer();

            OperationContext context = OperationContextManager.GetOperationContext(operation, branch, contextFactory);

            // Level 1 validators.
            foreach (ValidatorInfo info in ValidatorInfo.GetValidators(value, context, ValidationLevel.Level1))
            {
                IValidator validator = GetValidator(info.PluginType, context);
                if (validator != null)
                {
                    ValidationResultMessageContainer sub = Invoker.Invoke(validator, value, context);
                    if (sub.ContainsErrors)
                    {
                        throw new Level1ValidationException(ValidationResultMessage.ToString(sub.Errors));
                    }
                    whole.Add(sub);
                }
            }

            // Level 2 validators.
            foreach (ValidatorInfo info in ValidatorInfo.GetValidators(value, context, ValidationLevel.Level2))
            {
                IValidator validator = GetValidator(info.PluginType, context);
                if (validator != null)
                {
                    ValidationResultMessageContainer sub = Invoker.Invoke(validator, value, context);
                    whole.Add(sub);
                }
            }

            // Level 3 validators.
            foreach (ValidatorInfo info in ValidatorInfo.GetValidators(value, context, ValidationLevel.Level3))
            {
                IValidator validator = GetValidator(info.PluginType, context);
                if (validator != null)
                {
                    ValidationResultMessageContainer sub = Invoker.Invoke(validator, value, context);
                    whole.Add(sub);
                }
            }

            // Dispatch error messages and notification messages.
            if (whole.ContainsErrors || whole.ContainsNotifications)
            {
                InternalEnvelope<ValidationResultMessageContainer>.Dispatch(whole, contextFactory);                
            }

            return whole;
        }

        private static IValidator GetValidator(Type pluginType, OperationContext context)
        {
            IValidator validator = Activator.Activate(pluginType);

            foreach (IAuthorizeValidator authorizer in Authorizers)
            {
                if (authorizer.Authorize(validator, context) == false)
                {
                    return null;
                }
            }

            return validator;
        }

        /// <summary>
        /// Initialize.
        /// </summary>
        [Export(typeof(IInitializable))]
        private sealed class Initializer : IInitializable
        {
            void IInitializable.Initialize(InitializationContext context)
            {
                Authorizers = context.CompositionContainer.GetPlugins<IAuthorizeValidator>();
                Activator = context.CompositionContainer.GetPlugin<IValidatorActivator>();
                Invoker = context.CompositionContainer.GetPlugin<IValidatorInvoker>();
            }
        }

        /// <summary>
        /// Activates the validator.
        /// </summary>
        [Export(typeof(IValidatorActivator))]
        [DefaultPriority]
        private sealed class DefaultValidatorActivator : IValidatorActivator
        {
            IValidator IValidatorActivator.Activate(Type pluginType)
            {
                return (IValidator)ObjectFactory.CreateClassInstance(pluginType);
            }
        }

        /// <summary>
        /// Invokes the validator.
        /// </summary>
        [Export(typeof(IValidatorInvoker))]
        [DefaultPriority]
        private sealed class DefaultValidatorInvoker : IValidatorInvoker
        {
            ValidationResultMessageContainer IValidatorInvoker.Invoke(IValidator validator, object target, OperationContext context)
            {
                return validator.Validate(target, context);
            }
        }
    }
}
